#include "flashFs.h"
#include "logger_rte.h"
#include "kFs.h"
#include <cstring>


#include <Rtos.h>


extern "C" void core_debug(const char *format, ...);
lfs fs;
#ifdef USE_SPIFLASH
static SPIFlash *flash;
extern void *spi_lock;
void *lfs_lock_t = nullptr;

int lfs_erase(const lfs_config *cfg, lfs_block_t bt) {
#ifdef ARDUINO
    Rtos::mutex_lock(spi_lock);
#endif
    flash->blockErase4K(bt * cfg->block_size);
#ifdef ARDUINO
    Rtos::mutex_unlock(spi_lock);
#endif
    return 0;
}

static int lfs_read(const struct lfs_config *c, lfs_block_t block,
                    lfs_off_t off, void *buffer, lfs_size_t size) {
#ifdef ARDUINO
    Rtos::mutex_lock(spi_lock);
#endif
    flash->readBytes(block * c->block_size + off, (uint8_t *) buffer, (uint16_t) size);
#ifdef ARDUINO
    Rtos::mutex_unlock(spi_lock);
#endif
    return 0;
}

static int lfs_write(const struct lfs_config *c, lfs_block_t block,
                     lfs_off_t off, const void *buffer, lfs_size_t size) {
#ifdef ARDUINO
    Rtos::mutex_lock(spi_lock);
#endif
    flash->writeBytes(block * c->block_size + off, (const uint8_t *) buffer, (uint16_t) size);
#ifdef ARDUINO
    Rtos::mutex_unlock(spi_lock);
#endif
    return 0;
}

static int lfs_lock(const struct lfs_config *c) {
#ifdef ARDUINO
    return Rtos::mutex_lock(lfs_lock_t);
#endif
}

static int lfs_unlock(const struct lfs_config *c) {
#ifdef ARDUINO
    return Rtos::mutex_unlock(lfs_lock_t);
#endif
}

int lfs_sync(const struct lfs_config *) {
    return 0;
}
// uint8_t lfs_read_buf[256];
// uint8_t lfs_prog_buf[256];
// uint8_t lfs_lookahead_buf[512]; // 128/8=16
// uint8_t lfs_file_buf[256];

int FlashFs::begin(SPIFlash *spii, uint32_t block_count) {
    flash = spii;
    // flash->chipErase();
    cfg.erase = lfs_erase;
    cfg.read = lfs_read;
    cfg.prog = lfs_write;
    cfg.sync = lfs_sync;
    cfg.read_size = 256;
    cfg.prog_size = 256;
    cfg.block_size = 4096;
    cfg.block_count = block_count;
    cfg.cache_size = 256;
    cfg.lookahead_size = 256;
    cfg.block_cycles = 500;
    cfg.attr_max = 0;
    cfg.file_max = 0;
    cfg.name_max = 0;
    cfg.read_buffer = nullptr;      // lfs_read_buf;
    cfg.prog_buffer = nullptr;      // lfs_prog_buf;
    cfg.lookahead_buffer = nullptr; // lfs_lookahead_buf;
    // cfg.file_buffer = lfs_file_buf;
#ifdef LFS_THREADSAFE
    lfs_lock_t = Rtos::Create_Mutex();
    cfg.lock = lfs_lock;
    cfg.unlock = lfs_unlock;
#endif
    return mount();
}

void FlashFs::clear() {
    core_debug("Clear Flash!!!");
    flash->chipErase();
    HAL_Delay(1000);
}

uint32_t FlashFs::flash_size() {
    return flash->capacity();
}

#endif

int FlashFs::mount() {
    int res;
    res = lfs_mount(&fs, &cfg);
    if (res == 0) {
        core_debug("lfs mounted , size = %d kB\n", cfg.block_count * cfg.block_size / 1024);
    } else {
        core_debug("lfs mount failed: %d\n", res);
#ifndef LFS_READONLY
        format();
        res = mount();
#endif
    }
    return res;
}

int FlashFs::unmount() {
    int res;
    res = lfs_unmount(&fs);
    if (res == 0) {
        core_debug("lfs unmounted\n");
    } else {
        core_debug("lfs unmount failed: %d\n", res);
    }
    return res;
}

#ifndef LFS_READONLY

int FlashFs::format() {
    // lfs_unmount(&fs);
    core_debug("formatting lfs...\n");
    int res = lfs_format(&fs, &cfg);
    if (res != LFS_ERR_OK) {
        core_debug("lfs format failed: %d\n", res);
    }
    return res;
}

#endif

int FlashFs::dir(const char *dir_name, byte *buf, int buf_len) {
    lfs_dir_t d;
    struct lfs_info e
            {
            };

    lfs_dir_open(&fs, &d, dir_name);
    int len = 0;
    while (lfs_dir_read(&fs, &d, &e) > 0) {
        core_debug("%s  size:%i\n", e.name, e.size);
        if (buf_len > 0) {
            len += sprintf((char *) buf + len, "%s;%i;\n", e.name, e.size);
            buf_len -= len;
        }
    }
    lfs_dir_close(&fs, &d);
    return len;
}

int FlashFile::open_rdonly(const char *tfilename) {
    this->filename = tfilename;
    int res = lfs_file_open(&fs, &fd, tfilename, LFS_O_RDONLY);
    if (res < 0)
        core_debug("open_rdonly %s errno %d\n", tfilename, res);
    return res;
}

#ifndef LFS_READONLY

int FlashFile::open_write(const char *tfilename) {
    this->filename = tfilename;
    int res = lfs_file_open(&fs, &fd, tfilename, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC);
    if (res < 0)
        core_debug("open_write %s errno %d\n", tfilename, res);
    return res;
}

int FlashFile::open_append(const char *tfilename) {
    this->filename = tfilename;
    int res = lfs_file_open(&fs, &fd, tfilename, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND);
    if (res < 0)
        core_debug("open_write %s errno %d\n", tfilename, res);
    return res;
}

int FlashFile::write(const char *buf, int len) {
    int r = lfs_file_write(&fs, &fd, (void *) buf, len);
    if (r < 0)
        core_debug("write errno %d\n", r);
    return r;
}

int FlashFs::unlink(const char *filename) {
#ifdef RTE_APP
    logger.info("unlink %s.\n", filename);
#endif
    return lfs_remove(&fs, filename);
}

int FlashFile::unlink(const char *filename) {
    return lfs_remove(&fs, filename);
}

int flash_rename(const char *src, const char *dest) {
    return lfs_rename(&fs, src, dest);
}

int FlashFs::rename(const char *src, const char *dest) {
    return lfs_rename(&fs, src, dest);
}

int FlashFile::open_rw(const char *name) {
    this->filename = name;
    int res = lfs_file_open(&fs, &fd, name, LFS_O_RDWR | LFS_O_CREAT);
    if (res < 0)
        core_debug("open_write errno %d\n", res);
    return res;
}

int FlashFs::rmdir(const char *dirname) {
    return lfs_remove(&fs, dirname);
}

int FlashFs::mkdir(const char *dirname) {
    return lfs_mkdir(&fs, dirname);
}

#endif

int FlashFile::rewind() {
    return lfs_file_rewind(&fs, &fd);
}


int FlashFile::read(char *buf, int len) {
    int r = lfs_file_read(&fs, &fd, buf, len);
    if (r < 0)
        core_debug("errno %d\n", r);
    return r;
}

int FlashFile::close() {
    int r = 0;
    if (opened()) {
        r = lfs_file_close(&fs, &fd);
        if (r < 0)
            core_debug("errno %d\n", r);
    }
    kfs.data.changed = true;
    return r;
}


int FlashFile::size(const char *filename) const {
    lfs_info fdd{};
    int res = lfs_stat(&fs, filename, &fdd);
    if (res == LFS_ERR_OK) {
        return (int) fdd.size;
    }
    return res;
}

int FlashFile::opened() const {
    return lfs_mlist_isopen((struct lfs_mlist *) fs.mlist, (struct lfs_mlist *) &fd);
}

int FlashFile::stat(const char *filename) {
    lfs_info fdd{};
    memset(&fdd, 0, sizeof(fdd));
    lfs_stat(&fs, filename, &fdd);
    return (int) fdd.size;
}

void FlashFs::stat(const char *filename, lfs_info *fdd) {
    lfs_stat(&fs, filename, fdd);
}

int FlashFile::eof() const {
    if (fd.pos >= fd.ctz.size)
        return 1;
    return 0;
}

int FlashFile::fseek(int offs, int whence) {
    return lfs_file_seek(&fs, &fd, offs, whence);
}


int FlashFile::fflush() {
    return lfs_file_sync(&fs, &fd);
}

#ifdef ARDUINO

FlashFile *FlashFile::open(String string, const char *option) {
    if (*option == 'r') {
    }
    return nullptr;
}

#endif


int FlashFs::closedir(lfs_dir_t *dir) {
    return lfs_dir_close(&fs, dir);
}

int FlashFs::readdir(lfs_dir_t *dir, lfs_info *info) {
    return lfs_dir_read(&fs, dir, info);
}


int FlashFs::opendir(lfs_dir_t *dir, const char *dirname) {
    return lfs_dir_open(&fs, dir, dirname);
}

int FlashFs::dir_buf(const char *dir, char *buffer, int max_len) {
    lfs_dir_t d;
    struct lfs_info e
            {
            };
    int len;
    len = 0;
    lfs_dir_open(&fs, &d, dir);
    while (lfs_dir_read(&fs, &d, &e) > 0) {
        len += snprintf(buffer, max_len - len, "%s  size:%i\n", e.name, e.size);
    }
    lfs_dir_close(&fs, &d);
    return len;
}


